using System;
using System.Collections.Generic;

namespace needle.Services.astra.Collections
{
	/// <summary>
	/// <see cref="IndexBasedList{T}"/> based list.
	/// This list uses two lists - <see cref="List1"/> and <see cref="List2"/> - as items store.
	/// </summary>
	/// <remarks>
	/// <see cref="List1"/> uses inner indexes same as its own indexes.
	/// <see cref="List2"/> uses inner indexes of this form: -List2Index - 1.
	/// If <see cref="IsUnited"/> is true, <see cref="List1"/>`s items are first and <see cref="List2"/>`s items follow them.
	/// </remarks>
	/// <typeparam name="T1"><see cref="List1"/> item type.</typeparam>
	/// <typeparam name="T2"><see cref="List2"/> item type.</typeparam>
	public class TwoTypesIndexBasedList<T1, T2> : IndexBasedList<object>
	{
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="list1">See <see cref="List1"/>. If null, new <see cref="List{T1}"/> is used.</param>
		/// <param name="list2">See <see cref="List2"/>. If null, new <see cref="List{T2}"/> is used.</param>
		/// <exception cref="NotSupportedException">
		/// <typeparamref name="T1"/> and <typeparamref name="T2"/> are same.
		/// </exception>
		public TwoTypesIndexBasedList(IList<T1> list1, IList<T2> list2)
		{
			if (typeof(T1) == typeof(T2))
				throw new NotSupportedException("T1 and T2 types must be different.");
			if (list1 == null)
				list1 = new List<T1>();
			if (list2 == null)
				list2 = new List<T2>();
			_list1 = list1;
			IItemsChangeList<T1> itemsChangeList1 = list1 as IItemsChangeList<T1>;
			if (itemsChangeList1 != null)
				itemsChangeList1.ItemsChange += List1_ItemsChange;
			_list2 = list2;
			IItemsChangeList<T2> itemsChangeList2 = list2 as IItemsChangeList<T2>;
			if (itemsChangeList2 != null)
				itemsChangeList2.ItemsChange += List2_ItemsChange;
		}

		/// <summary>
		/// Internal list 1.
		/// </summary>
		public IList<T1> List1
		{
			get { return _list1; }
		}
		/// <summary>
		/// Internal list 2.
		/// </summary>
		public IList<T2> List2
		{
			get { return _list2; }
		}

		/// <summary>
		/// See <see cref="IndexBasedList{}.UnitedCount"/>.
		/// </summary>
		/// <value>
		/// <see cref="List1"/>.<see cref="IList{}.Count"/> + <see cref="List2"/>.<see cref="IList{}.Count"/>.
		/// </value>
		public override int UnitedCount
		{
			get { return this.List1.Count + this.List2.Count; }
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.IsReadOnly"/>.
		/// </summary>
		/// <value>
		/// <see cref="List1"/>.<see cref="IList{}.IsReadOnly"/> or <see cref="List2"/>.<see cref="IList{}.IsReadOnly"/>.
		/// </value>
		public override bool IsReadOnly
		{
			get { return this.List1.IsReadOnly || this.List2.IsReadOnly; }
		}

		/// <summary>
		/// See <see cref="IndexBasedList{}.GetItem"/>.
		/// </summary>
		/// <remarks>
		/// Uses <see cref="List1"/> for <paramref name="innerIndex"/> &gt;= 0, otherwise <see cref="List2"/>.
		/// </remarks>
		protected override object GetItem(int innerIndex)
		{
			if (innerIndex >= 0)
				return this.List1[innerIndex];
			else
				return this.List2[TransformList2Index(innerIndex)];
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.SetItem"/>.
		/// </summary>
		/// <remarks>
		/// Uses <see cref="List1"/> for <paramref name="innerIndex"/> &gt;= 0, otherwise <see cref="List2"/>.
		/// Calls <see cref="ValidateItemType"/>.
		/// </remarks>
		protected override void SetItem(int innerIndex, object item)
		{
			ValidateItemType(item);
			if (innerIndex >= 0)
				this.List1[innerIndex] = (T1)item;
			else
				this.List2[TransformList2Index(innerIndex)] = (T2)item;
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.InsertItem"/>.
		/// </summary>
		/// <remarks>
		/// Calls <see cref="ValidateItemType"/>.
		/// Uses <see cref="List1"/> when <paramref name="item"/> is of <typeparamref name="T1"/> type.
		/// Uses <see cref="List2"/> when <paramref name="item"/> is of <typeparamref name="T2"/> type.
		/// </remarks>
		/// <exception cref="ArgumentException">
		/// <paramref name="item"/> type is neither <typeparamref name="T1"/> nor <typeparamref name="T2"/>.
		/// </exception>
		protected override int InsertItem(int index, object item)
		{
			ValidateItemType(item);
			// T1
			if (item is T1)
			{
				if (!this.IsUnited)
					index = this.List1.Count;
				this.List1.Insert(index, (T1)item);
			}
			// T2
			else
			{
				if (this.IsUnited)
					index -= this.List1.Count;
				else
					index = this.List2.Count;
				this.List2.Insert(index, (T2)item);
				index = TransformList2Index(index);
			}
			return index;
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.RemoveItem"/>.
		/// </summary>
		/// <remarks>
		/// Uses <see cref="List1"/> for <paramref name="innerIndex"/> &gt;= 0, otherwise <see cref="List2"/>.
		/// </remarks>
		protected override void RemoveItem(int innerIndex)
		{
			if (innerIndex >= 0)
				this.List1.RemoveAt(innerIndex);
			else
				this.List2.RemoveAt(TransformList2Index(innerIndex));
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.ClearItems"/>.
		/// </summary>
		/// <remarks>Clears both <see cref="List1"/> and <see cref="List2"/>.</remarks>
		protected override void ClearItems()
		{
			this.List1.Clear();
			this.List2.Clear();
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.GetUnitedInnerIndex"/>.
		/// </summary>
		/// <remarks>
		/// Uses <see cref="List1"/> for <paramref name="innerIndex"/> &gt;= 0, otherwise <see cref="List2"/>.
		/// </remarks>
		/// <returns>
		/// <paramref name="index"/> if it is &lt; <see cref="List1"/>.<see cref="IList{T1}.Count"/>, otherwise <see cref="TransformList2Index"/>(index - List1.Count).
		/// </returns>
		protected override int GetUnitedInnerIndex(int index)
		{
			int list1Count = this.List1.Count;
			if (index < list1Count)
				return index;
			else
				return TransformList2Index(index - list1Count);
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.GetUnitedIndex"/>.
		/// </summary>
		protected override int GetUnitedIndex(int innerIndex)
		{
			if (innerIndex >= 0)
				return innerIndex;
			else
				return TransformList2Index(innerIndex) + this.List1.Count;
		}
		/// <summary>
		/// See <see cref="IndexBasedList{}.GetLastUnitedIndexInSubstore"/>.
		/// </summary>
		/// <remarks>Calls <see cref="ValidateItemType"/>.</remarks>
		protected override int GetLastUnitedIndexInSubstore(object item)
		{
			ValidateItemType(item);
			if (item is T1)
				return this.List1.Count - 1;
			else
				return this.UnitedCount - 1;
		}
		/// <summary>
		/// Validates <paramref name="item"/>.
		/// </summary>
		/// <param name="item">Item.</param>
		/// <exception cref="ArgumentException">
		/// <paramref name="item"/> type is neither <typeparamref name="T1"/> nor <typeparamref name="T2"/>.
		/// </exception>
		protected static void ValidateItemType(object item)
		{
			if (!(item is T1 || item is T2))
				throw new ArgumentException(
					string.Format("Item type {0} is neither {1} nor {2}.", item.GetType(), typeof(T1), typeof(T2)), "item");
		}
		/// <summary>
		/// Transforms <paramref name="index"/> for <see cref="List2"/>.
		/// </summary>
		/// <param name="index">Index.</param>
		/// <returns>-<paramref name="index"/> - 1.</returns>
		protected static int TransformList2Index(int index)
		{
			return -1 * index - 1;
		}

		/// <summary>
		/// Translates <see cref="IItemsChangeList{}.ItemsChange"/> event of <see cref="List1"/> if it implements it.
		/// </summary>
		private void List1_ItemsChange(object sender, ListItemsChangeEventArgs<T1> e)
		{
			TranslateInnerItemsChange(e.Change, e.NewItem, e.OldItem, e.NewIndex, e.OldIndex);
		}
		/// <summary>
		/// Translates <see cref="IItemsChangeList{}.ItemsChange"/> event of <see cref="List2"/> if it implements it.
		/// </summary>
		private void List2_ItemsChange(object sender, ListItemsChangeEventArgs<T2> e)
		{
			int list1Count = this.List1.Count;
			TranslateInnerItemsChange(e.Change,
				e.NewItem, e.OldItem, e.NewIndex + list1Count, e.OldIndex + list1Count);
		}

		/// <summary>
		/// See <see cref="List1"/>.
		/// </summary>
		private IList<T1> _list1;
		/// <summary>
		/// See <see cref="List2"/>.
		/// </summary>
		private IList<T2> _list2;
	}
}
